package com.gxar.navigation.server.base.common.utils;


import com.gxar.navigation.server.base.model.arithmetic.dto.PointDTO;
import com.gxar.navigation.server.base.model.arithmetic.dto.RouteSegmentDTO;

import java.util.ArrayList;
import java.util.List;

/**
 * 查找点到最近到线计算
 *
 * @author linsy
 * @version 1.0
 * @created 2023/9/15 10:04
 */
public class NearestPointLineCalculateUtils {

    /**
     * 查询距离点最近点线，如果点在线的宽度之外返回null
     *
     * @param segments    路段集合
     * @param targetPoint 点
     * @return 距离点最近的线
     */
    public static RouteSegmentDTO findPointNearestLine(List<RouteSegmentDTO> segments, PointDTO targetPoint) {
        RouteSegmentDTO nearestRouteSegment = null;
        double minDistance = Double.MAX_VALUE;

        for (RouteSegmentDTO segment : segments) {
            double distance = calculateDistanceToPoint(segment, targetPoint);
            if (distance <= (segment.getRadius() * 2) && distance < minDistance) {
                minDistance = distance;
                nearestRouteSegment = segment;
            }
        }

        return nearestRouteSegment;
    }

    /**
     * 计算到点的距离
     *
     * @param segment     线
     * @param targetPoint 点
     * @return 返回距离长度（单位：米）
     */
    private static double calculateDistanceToPoint(RouteSegmentDTO segment, PointDTO targetPoint) {
        // 计算点到路段的最近距离
        double distanceToStart = calculateHaversineDistance(segment.getStartPoint(), targetPoint);
        double distanceToEnd = calculateHaversineDistance(segment.getEndPoint(), targetPoint);

        // 如果点在路段的起始点之前，则距离取起始点距离
        if (isPointBeforeSegment(segment, targetPoint)) {
            return distanceToStart;
        }
        // 如果点在路段的终点之后，则距离取终点距离
        else if (isPointAfterSegment(segment, targetPoint)) {
            return distanceToEnd;
        }
        // 否则，距离取点到路段的垂直距离
        else {
            return calculatePerpendicularDistance(segment, targetPoint);
        }
    }

    /**
     * 检查点是否在路段的起始点之前
     * 如果点的经度小于等于路段的起始点经度，则认为在之前
     *
     * @param segment 路段
     * @param point   点
     * @return 是否在起始点之前
     */
    private static boolean isPointBeforeSegment(RouteSegmentDTO segment, PointDTO point) {
        return point.getLongitude() <= Math.min(segment.getStartPoint().getLongitude(), segment.getEndPoint().getLongitude());
    }

    /**
     * 检查点是否在路段的终点之后
     * 如果点的经度大于等于路段的终点经度，则认为在之后
     *
     * @param segment 路段
     * @param point   点
     * @return 是否起始点之后
     */
    private static boolean isPointAfterSegment(RouteSegmentDTO segment, PointDTO point) {
        // 检查点是否在路段的终点之后
        // 如果点的经度大于等于路段的终点经度，则认为在之后
        return point.getLongitude() >= Math.max(segment.getStartPoint().getLongitude(), segment.getEndPoint().getLongitude());
    }

    /**
     * 计算垂直距离（单位：米）
     *
     * @param segment 路段
     * @param point   点
     * @return 垂直距离（单位：米）
     */
    private static double calculatePerpendicularDistance(RouteSegmentDTO segment, PointDTO point) {
        // 计算点到路段的垂直距离
        // 通过将路段视为直线，计算点到直线的距离
        double x1 = segment.getStartPoint().getLongitude();
        double y1 = segment.getStartPoint().getLatitude();
        double x2 = segment.getEndPoint().getLongitude();
        double y2 = segment.getEndPoint().getLatitude();
        double x0 = point.getLongitude();
        double y0 = point.getLatitude();

        double numerator = Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1);
        double denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));

        return numerator / denominator;
    }

    /**
     * 使用Haversine公式计算两点之间的距离
     *
     * @param start 开始点位
     * @param end   结束点位
     * @return 距离（单位：米）
     */
    public static double calculateHaversineDistance(PointDTO start, PointDTO end) {
        double earthRadius = 6371000.0; // 地球半径（单位：米）
        double lat1 = Math.toRadians(start.getLatitude());
        double lon1 = Math.toRadians(start.getLongitude());
        double lat2 = Math.toRadians(end.getLatitude());
        double lon2 = Math.toRadians(end.getLongitude());

        double dLat = lat2 - lat1;
        double dLon = lon2 - lon1;

        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2);

        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return earthRadius * c;
    }

    public static void main(String[] args) {
        // 假设你有一组路段的起始和结束经纬度坐标以及宽度（以米为单位）
        List<RouteSegmentDTO> routeSegments = new ArrayList<>();
        routeSegments.add(new RouteSegmentDTO(1L, new PointDTO(1L, 118.123569, 24.437475), new PointDTO(3L, 118.121418, 24.437902), 4)); // 示例线段数据
        routeSegments.add(new RouteSegmentDTO(2L, new PointDTO(2L, 118.123578, 24.437688), new PointDTO(4L, 118.121472, 24.437956), 3)); // 示例线段数据
        // 添加更多路段...

        // 假设你有一个点的经纬度坐标
        PointDTO targetPoint = new PointDTO(5L, 118.121077, 24.438075); // 要查找的点

        RouteSegmentDTO nearestRouteSegment = findPointNearestLine(routeSegments, targetPoint);

        if (nearestRouteSegment != null) {
            System.out.println("距离最近的路段坐标：");
            System.out.println("起点：" + nearestRouteSegment.getStartPoint());
            System.out.println("终点：" + nearestRouteSegment.getEndPoint());
            System.out.println("半径：" + nearestRouteSegment.getRadius() + " 米");

            double distance = calculateDistanceToPoint(nearestRouteSegment, targetPoint);
            System.out.println("距离最近路段的距离：" + distance + " 米");
        } else {
            System.out.println("未找到符合条件的路段。");
        }
    }

}
